#include "vsf.h"
#include <stdio.h>
#include <stdlib.h>

static bool vsfjson_token_match(const char *json, const char *value)
{
	int n = strlen(value);
	return !strncmp(json, value, n) && !isalnum(json[n]);
}

enum vsfjson_type_t vsfjson_get_type(const char *json)
{
	enum vsfjson_type_t type = VSFJSON_TYPE_INVALID;
	char ch = *json;

	if (ch == '{')
		type = VSFJSON_TYPE_OBJECT;
	else if (ch == '[')
		type = VSFJSON_TYPE_ARRAY;
	else if (ch == '"')
		type = VSFJSON_TYPE_STRING;
	else if ((ch >= '0') && (ch <= '9'))
		type = VSFJSON_TYPE_NUMBER;
	else if (vsfjson_token_match(json, "true") || vsfjson_token_match(json, "false"))
		type = VSFJSON_TYPE_BOOLEAN;
	else if (vsfjson_token_match(json, "null"))
		type = VSFJSON_TYPE_NULL;
	return type;
}

static char *vsfjson_skip_space(const char *json)
{
	while ((*json != '\0') && isspace(*json)) json++;
	return *json == '\0' ? NULL : (char *)json;
}

enum vsfjson_type_t vsfjson_enumerate_start(
		struct vsfjson_enumerator_t *e, const char *json)
{
	enum vsfjson_type_t type = vsfjson_get_type(json);

	if ((type != VSFJSON_TYPE_OBJECT) && (type != VSFJSON_TYPE_ARRAY))
		type = VSFJSON_TYPE_INVALID;
	else
	{
		e->ptr = (char *)json;
		e->type = type;
		e->first = true;
		e->ptr = vsfjson_skip_space(++e->ptr);
		if (!e->ptr) type = VSFJSON_TYPE_INVALID;
	}
	return type;
}

char *vsfjson_enumerate_next(struct vsfjson_enumerator_t *e)
{
	char *cur = e->ptr;
	bool str = false;
	int brace_level = 0, square_level = 0;

	if (e->first)
	{
		e->first = false;
		if (*cur == ',') return NULL;
		goto exit;
	}
	else
	{
		while (*cur != '\0')
		{
			switch (*cur)
			{
			case '\"':
				str = !str;
				break;
			case '\\':
				if (!str) return NULL;
				cur++;
				break;
			case '{':
				if (!str)
					brace_level++;
				break;
			case '[':
				if (!str)
					square_level++;
				break;
			case ',':
				if (!str && !brace_level && !square_level)
				{
					cur = vsfjson_skip_space(++cur);
					goto exit;
				}
				break;
			case '}':
				if (!str)
				{
					brace_level--;
					if (brace_level < 0)
						return NULL;
					goto check;
				}
				break;
			case ']':
				if (!str)
				{
					square_level--;
					if (square_level < 0)
						return NULL;
				check:
					if (!brace_level && !square_level)
					{
						cur = vsfjson_skip_space(++cur);
						if (*cur == ',')
						{
							cur = vsfjson_skip_space(++cur);
							goto exit;
						}
						else
						{
							return NULL;
						}
					}
				}
				break;
			}
			cur++;
		}

	exit:
		if ((*cur == '}') || (*cur == ']'))
			return NULL;
	}
	e->ptr = cur;
	return cur;
}

static bool vsfjson_isdiv(char ch)
{
	return (ch == '/') || (ch == '\\');
}

int vsfjson_num_of_entry(const char *json)
{
	struct vsfjson_enumerator_t e;
	int num_of_entry = 0;

	if (vsfjson_enumerate_start(&e, json) != VSFJSON_TYPE_INVALID)
		while (vsfjson_enumerate_next(&e) != NULL)
			num_of_entry++;
	return num_of_entry;
}

char *vsfjson_get(const char *json, const char *key)
{
	struct vsfjson_enumerator_t e;
	unsigned long idx, curidx;
	char *cur;

	if (vsfjson_isdiv(*key))
		key++;

	while (*key != '\0')
	{
		if (VSFJSON_TYPE_INVALID == vsfjson_enumerate_start(&e, json))
			return NULL;

		if (e.type == VSFJSON_TYPE_ARRAY)
		{
			idx = strtoul(key, (char **)&key, 0);
			curidx = 0;
		}
		else
		{
			for (idx = 0; key[idx] && !vsfjson_isdiv(key[idx]); idx++);
		}

		while (1)
		{
			cur = vsfjson_enumerate_next(&e);
			if (!cur) return NULL;

			if (e.type == VSFJSON_TYPE_ARRAY)
			{
				if (curidx++ == idx)
				{
					json = cur;
					break;
				}
			}
			else
			{
				if (*cur++ != '\"') return NULL;
				if (!strncmp(key, cur, idx) && (cur[idx] == '\"'))
				{
					key += idx;
					if (*key)
						key++;
					cur += idx + 1;
					cur = vsfjson_skip_space(cur);
					if (*cur++ != ':') return NULL;

					json = vsfjson_skip_space(cur);
					break;
				}
			}
		}
	}
	return (char *)json;
}

int vsfjson_get_string(const char *json, char *result, int len)
{
	char ch;
	int curlen = 0;

	if (*json++ != '"')
		return -1;

	do {
		ch = *json++;
		if (ch == '\\')
		{
			ch = *json++;
			if (result && (curlen >= len))
				return VSFERR_NOT_ENOUGH_RESOURCES;
			switch (ch)
			{
			case '"':	if (result) result[curlen] = '\"';	curlen++;break;
			case '\\':	if (result) result[curlen] = '\\';	curlen++;break;
			case '/':	if (result) result[curlen] = '/';	curlen++;break;
			case 'b':	if (result) result[curlen] = '\b';	curlen++;break;
			case 'f':	if (result) result[curlen] = '\f';	curlen++;break;
			case 'n':	if (result) result[curlen] = '\n';	curlen++;break;
			case 'r':	if (result) result[curlen] = '\r';	curlen++;break;
			case 't':	if (result) result[curlen] = '\t';	curlen++;break;
			case 'u':
				// TODO:
				break;
			default:
				return -1;
			}
		}
		else if (ch != '\"')
		{
			if (result && (curlen >= len))
				return VSFERR_NOT_ENOUGH_RESOURCES;
			if (result)
				result[curlen] = ch;
			curlen++;
		}
	} while (ch != '\"');

	if (result && (curlen >= len))
		return VSFERR_NOT_ENOUGH_RESOURCES;
	if (result)
		result[curlen] = '\0';
	return ++curlen;
}

int vsfjson_get_number(const char *json, double *result)
{
	double value = strtod(json, NULL);
	if (result) *result = value;
	return sizeof(*result);
}

int vsfjson_get_boolean(const char *json, bool *result)
{
	bool value;
	if (vsfjson_token_match(json, "true"))
		value = true;
	else if (vsfjson_token_match(json, "false"))
		value = false;
	else return -1;
	if (result) *result = value;
	return sizeof(*result);
}

// constructor
void vsfjson_constructor_init(struct vsfjson_constructor_t *c, void *param,
		int (*write_str)(void *, char *, int))
{
	c->write_str = write_str;
	if (write_str != NULL)
		c->param = param;
	else
		c->len = 0;
	c->first = true;
	c->result = true;
}

int vsfjson_write_str(struct vsfjson_constructor_t *c, char *buf, int len)
{
	if (c->write_str != NULL)
	{
		int ret = c->write_str(c->param, buf, len);
		if (ret < 0) c->result = false;
		return ret;
	}
	else
	{
		c->len += len;
		return 0;
	}
}

static int vsfjson_set_token(struct vsfjson_constructor_t *c, char *token)
{
	int n = strlen(token);
	return vsfjson_write_str(c, token, n);
}

int vsfjson_set_key(struct vsfjson_constructor_t *c, char *key)
{
	int ret;

	if (c->first)
		c->first = false;
	else
	{
		ret = vsfjson_write_str(c, ",", 1);
		if (ret < 0) return ret;
	}

	if (key != NULL)
	{
		ret = vsfjson_write_str(c, "\"", 1);
		if (ret < 0) return ret;
		ret = vsfjson_set_token(c, key);
		if (ret < 0) return ret;
		ret = vsfjson_write_str(c, "\"", 1);
		if (ret < 0) return ret;

		ret = vsfjson_write_str(c, ":", 1);
		if (ret < 0) return ret;
	}
	return 0;
}

int vsfjson_set_string(struct vsfjson_constructor_t *c, char *key, char *value)
{
	int ret;
	char ch, buf[2];

	ret = vsfjson_set_key(c, key);
	if (ret < 0) return ret;
	ret = vsfjson_write_str(c, "\"", 1);
	if (ret < 0) return ret;

	do {
		ch = *value++;
		switch (ch)
		{
		case '\0':	break;
		case '\"':	buf[1] = '"';	goto set_esc;
		case '\\':	buf[1] = '\\';	goto set_esc;
		case '/':	buf[1] = '/';	goto set_esc;
		case '\b':	buf[1] = 'b';	goto set_esc;
		case '\f':	buf[1] = 'b';	goto set_esc;
		case '\n':	buf[1] = 'n';	goto set_esc;
		case '\r':	buf[1] = 'r';	goto set_esc;
		case '\t':	buf[1] = 't';
		set_esc:
			buf[0] = '\\';
			ret = vsfjson_write_str(c, buf, 2);
			if (ret < 0) return ret;
			break;
		default:
			buf[0] = ch;
			ret = vsfjson_write_str(c, buf, 1);
			if (ret < 0) return ret;
			break;
		}
	} while (ch != '\0');

	ret = vsfjson_write_str(c, "\"", 1);
	return (ret < 0) ? ret : 0;
}

int vsfjson_set_number(struct vsfjson_constructor_t *c, char *key, double value)
{
	int ret;
	char buf[64];

	ret = vsfjson_set_key(c, key);
	if (ret < 0) return ret;

	ret = snprintf(buf, sizeof(buf), "%f", value);
	ret = vsfjson_write_str(c, buf, ret);
	return (ret < 0) ? ret : 0;
}

int vsfjson_set_boolean(struct vsfjson_constructor_t *c, char *key, bool value)
{
	if (vsfjson_set_key(c, key) < 0)
		return -1;
	return vsfjson_set_token(c, value ? "true" : "false");
}

int vsfjson_set_null(struct vsfjson_constructor_t *c, char *key)
{
	if (vsfjson_set_key(c, key) < 0)
		return -1;
	return vsfjson_set_token(c, "null");
}
